//===- lib/MC/MCKifyAsmStreamer.cpp - Dummy Streamer Implementation ----------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "llvm/ADT/StringRef.h"
#include "llvm/MC/MCFixupKindInfo.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCStreamer.h"
#include "llvm/MC/MCSymbol.h"

#include "llvm/MC/MCAsmBackend.h"
#include "llvm/MC/MCAsmInfo.h"
#include "llvm/MC/MCAssembler.h"
#include "llvm/MC/MCCodeEmitter.h"
#include "llvm/MC/MCCodeView.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCExpr.h"
#include "llvm/MC/MCFixupKindInfo.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCInstPrinter.h"
#include "llvm/MC/MCObjectFileInfo.h"
#include "llvm/MC/MCObjectWriter.h"
#include "llvm/MC/MCRegister.h"
#include "llvm/MC/MCRegisterInfo.h"
#include "llvm/MC/MCSectionMachO.h"
#include "llvm/MC/MCStreamer.h"
#include "llvm/Support/TargetRegistry.h"

#include <iostream>

using namespace llvm;

namespace {

class MCKifyAsmStreamer : public MCStreamer {
public:
    // MCKifyAsmStreamer(MCContext& Context)
    //     : MCStreamer(Context)
    // {
    // }
    const MCAsmInfo* MAI;

    MCKifyAsmStreamer(MCContext& Context, std::unique_ptr<formatted_raw_ostream> os,
        bool isVerboseAsm, bool useDwarfDirectory,
        MCInstPrinter* printer, std::unique_ptr<MCCodeEmitter> emitter,
        std::unique_ptr<MCAsmBackend> asmbackend, bool showInst)
        : MCStreamer(Context)
        // , OSOwner(std::move(os))
        // , OS(*OSOwner)
        , MAI(Context.getAsmInfo())
        // , InstPrinter(printer)
        , Assembler(std::make_unique<MCAssembler>(
              Context, std::move(asmbackend), std::move(emitter),
              (asmbackend) ? asmbackend->createObjectWriter(NullStream)
                           : nullptr))
    // , CommentStream(CommentToEmit)
    // , IsVerboseAsm(isVerboseAsm)
    // , ShowInst(showInst)
    // , UseDwarfDirectory(useDwarfDirectory)
    {
        // assert(InstPrinter);
        // if (IsVerboseAsm)
        //     InstPrinter->setCommentStream(CommentStream);
        // if (Assembler->getBackendPtr())
        //     setAllowAutoPadding(Assembler->getBackend().allowAutoPadding());
    }

    /// @name MCStreamer Interface
    /// @{

    bool hasRawTextSupport() const override { return true; }
    void EmitRawTextImpl(StringRef String) override {}

    bool EmitSymbolAttribute(MCSymbol* Symbol,
        MCSymbolAttr Attribute) override
    {
        return true;
    }

    void EmitCommonSymbol(MCSymbol* Symbol, uint64_t Size,
        unsigned ByteAlignment) override {}
    void EmitZerofill(MCSection* Section, MCSymbol* Symbol = nullptr,
        uint64_t Size = 0, unsigned ByteAlignment = 0,
        SMLoc Loc = SMLoc()) override {}
    void EmitGPRel32Value(const MCExpr* Value) override {}
    void BeginCOFFSymbolDef(const MCSymbol* Symbol) override {}
    void EmitCOFFSymbolStorageClass(int StorageClass) override {}
    void EmitCOFFSymbolType(int Type) override {}
    void EndCOFFSymbolDef() override {}

    /// Add a comment showing the encoding of an instruction.
    void AddEncodingComment(const MCInst& Inst, const MCSubtargetInfo&);
    void EmitInstruction(const MCInst& Inst, const MCSubtargetInfo& STI) override;

    MCAssembler& getAssembler() { return *Assembler; }

    std::unique_ptr<MCAssembler> Assembler;
    raw_null_ostream NullStream;
};

}

void MCKifyAsmStreamer::AddEncodingComment(const MCInst& Inst,
    const MCSubtargetInfo& STI)
{
    raw_ostream& OS = GetCommentOS();
    SmallString<256> Code;
    SmallVector<MCFixup, 4> Fixups;
    raw_svector_ostream VecOS(Code);

    // If we have no code emitter, don't emit code.
    if (!getAssembler().getEmitterPtr())
        return;

    getAssembler().getEmitter().encodeInstruction(Inst, VecOS, Fixups, STI);

    // If we are showing fixups, create symbolic markers in the encoded
    // representation. We do this by making a per-bit map to the fixup item index,
    // then trying to display it as nicely as possible.
    SmallVector<uint8_t, 64> FixupMap;
    FixupMap.resize(Code.size() * 8);
    for (unsigned i = 0, e = Code.size() * 8; i != e; ++i)
        FixupMap[i] = 0;

    for (unsigned i = 0, e = Fixups.size(); i != e; ++i) {
        MCFixup& F = Fixups[i];
        const MCFixupKindInfo& Info = getAssembler().getBackend().getFixupKindInfo(F.getKind());
        for (unsigned j = 0; j != Info.TargetSize; ++j) {
            unsigned Index = F.getOffset() * 8 + Info.TargetOffset + j;
            assert(Index < Code.size() * 8 && "Invalid offset in fixup!");
            FixupMap[Index] = 1 + i;
        }
    }
#define OS std::cout
    // FIXME: Note the fixup comments for Thumb2 are completely bogus since the
    // high order halfword of a 32-bit Thumb2 instruction is emitted first.
    OS << "encoding: [";
    for (unsigned i = 0, e = Code.size(); i != e; ++i) {
        if (i)
            OS << ',';

        // See if all bits are the same map entry.
        uint8_t MapEntry = FixupMap[i * 8 + 0];
        for (unsigned j = 1; j != 8; ++j) {
            if (FixupMap[i * 8 + j] == MapEntry)
                continue;

            MapEntry = uint8_t(~0U);
            break;
        }
        printf("%d: %x\n", i, Code[i]);
        
        if (MapEntry != uint8_t(~0U)) {
            if (MapEntry == 0) {
                // OS << format("0x%02x", uint8_t(Code[i]));
                OS << std::hex << (Code[i]);
            } else {
                if (Code[i]) {
                    // FIXME: Some of the 8 bits require fix up.
                    // OS << format("0x%02x", uint8_t(Code[i])) << '\''
                    OS << std::hex << Code[i] << '\''
                       << char('A' + MapEntry - 1) << '\'';
                } else
                    OS << char('A' + MapEntry - 1);
            }
        } else {
            // Otherwise, write out in binary.
            OS << "0b";
            for (unsigned j = 8; j--;) {
                unsigned Bit = (Code[i] >> j) & 1;

                unsigned FixupBit;
                if (MAI->isLittleEndian())
                    FixupBit = i * 8 + j;
                else
                    FixupBit = i * 8 + (7 - j);

                if (uint8_t MapEntry = FixupMap[FixupBit]) {
                    assert(Bit == 0 && "Encoder wrote into fixed up bit!");
                    OS << char('A' + MapEntry - 1);
                } else
                    OS << Bit;
            }
        }
    }
    OS << "]\n";

    for (unsigned i = 0, e = Fixups.size(); i != e; ++i) {
        MCFixup& F = Fixups[i];
        const MCFixupKindInfo& Info = getAssembler().getBackend().getFixupKindInfo(F.getKind());
        OS << "  fixup " << char('A' + i) << " - "
           << "offset: " << F.getOffset();
        //    << ", value: " << *F.getValue() << ", kind: " << Info.Name << "\n";
    }
#undef OS
}

void MCKifyAsmStreamer::EmitInstruction(const MCInst& Inst,
    const MCSubtargetInfo& STI)
{
    assert(getCurrentSectionOnly() && "Cannot emit contents before setting section!");

    // Show the encoding in a comment if we have a code emitter.
    AddEncodingComment(Inst, STI);

    // // Show the MCInst if enabled.
    // if (ShowInst) {
    //     Inst.dump_pretty(GetCommentOS(), InstPrinter.get(), "\n ");
    //     GetCommentOS() << "\n";
    // }

    // if (getTargetStreamer())
    //     getTargetStreamer()->prettyPrintAsm(*InstPrinter, 0, Inst, STI, OS);
    // else
    //     InstPrinter->printInst(&Inst, 0, "", STI, OS);

    // StringRef Comments = CommentToEmit;
    // if (Comments.size() && Comments.back() != '\n')
    //     GetCommentOS() << "\n";

    // EmitEOL();
}

#include "inc.h"

llvm::MCStreamer* llvm::kifycreateAsmStreamer(MCContext& Context,
    std::unique_ptr<formatted_raw_ostream> OS,
    bool isVerboseAsm, bool useDwarfDirectory,
    MCInstPrinter* IP,
    std::unique_ptr<MCCodeEmitter>&& CE,
    std::unique_ptr<MCAsmBackend>&& MAB,
    bool ShowInst)
{
    return new MCKifyAsmStreamer(Context, std::move(OS), isVerboseAsm,
        useDwarfDirectory, IP, std::move(CE), std::move(MAB),
        ShowInst);
}

llvm::MCStreamer* llvm::kify_nncreateAsmStreamer(MCContext& Ctx,
    std::unique_ptr<formatted_raw_ostream> OS,
    bool IsVerboseAsm, bool UseDwarfDirectory,
    MCInstPrinter* InstPrint,
    std::unique_ptr<MCCodeEmitter>&& CE,
    std::unique_ptr<MCAsmBackend>&& TAB,
    bool ShowInst, const Target* TheTarget)
{
    formatted_raw_ostream& OSRef = *OS;
    MCStreamer* S = kifycreateAsmStreamer(
        Ctx, std::move(OS), IsVerboseAsm, UseDwarfDirectory, InstPrint,
        std::move(CE), std::move(TAB), ShowInst);
    TheTarget->createAsmTargetStreamer(*S, OSRef, InstPrint, IsVerboseAsm);
    return S;
}
